import { Singleton } from "../../src/core/Class";
import Map from "../../src/core/Map";

/**
 * 扑克逻辑值，最好资源名也按此命名
 */
export const Pokers = [
    1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, // ♦A-♦K
    17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, // ♥A-♥K
    33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, // ♣A-♣K
    49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // ♠A-♠K
    62, 63, // 小大王
];

// 特殊牌值
export enum SpecialValue{
    num_A = 14,
    num_2 = 15,
    imp  = 62, // 小王
    king = 63, // 大王
}

/**
 * 扑克花色枚举
 */
export enum PokerSuit {
    unknow = -1,
    king=0,         // 大王
    imp,            // 小王
    spade,          // 黑桃
    heart,          // 红杏
    club,           // 梅花
    diamond,        // 方块
};

/**
 * 扑克组合枚举
 */
export enum PokerType {
    unknow = -1, // 错误的类型
    single, // 单牌
    pairs,   // 对子
    joker, // 王炸 
    three, // 三不带
    three_single, // 3带1  3334
    three_pairs, // 3带对  33344
    bomb, // 炸弹
    four_Single, // 4带单     333344
    four_pairs, // 4带2对 33334455
    progression, // 顺子     34567 ≥5张但不包括2点和双王
    progression_pairs, // 双顺 334455 ≥6张但不包括2点和双王
    plane, // 飞机不带 333444
    plane_single, // 飞机带单 33344456
    plane_pair, // 飞机带带对 33344455
    plane_pairs, // 飞机带双顺 3334445566
}

/**
 * 组合权重枚举，在AI机器人或者牌型提示中合理的选择组合
 */
export enum  PokerWeight{
    unknow = -1,
    single = 0, // 单牌
    pairs = 20,   // 对子
    joker = 120, // 王炸
    three = 40, // 三不带
    three_single = 40, // 3带1
    three_pairs = 80, // 3带对
    bomb = 100, // 炸弹
    four_Single = 80, // 4带2
    four_pairs = 80, // 4带2对
    progression = 60, // 顺子
    progression_pairs = 80, // 连对
    plane = 80, // 飞机不带
    plane_single = 80, // 飞机带单
    plane_pair = 80, // 飞机带对子
    plane_pairs = 80, // 飞机带对子
};

/**
 * 牌型权重
 */
export const WEIGHT = {
    // 单牌权重
    single: { "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1,
        "10": 1, "11": 2, "12": 3, "13": 4, "14": 4, "15": 6, "62": 8, "63": 8 },
    // 对子权重
    pair: { "3": 4, "4": 4, "5": 5, "6": 5, "7": 5, "8": 5, "9": 6,
        "10": 6, "11": 7, "12": 8, "13": 9, "14": 10, "15": 12 },
    // 三条权重
    three: { "3": 9, "4": 9, "5": 10, "6": 10, "7": 11,
        "8": 11, "9": 12, "10": 12, "11": 13, "12": 13, "13": 15, "14": 18, "15": 20 },
    // 单顺的权重
    progression: { "5": 15, "6": 17, "7": 19, "8": 21, "9": 22, "10": 23, "11": 24, "12": 25 },
    // 双顺的权重
    progression_pairs: { "3": 17, "4": 21, "5": 24, "6": 28, "7": 32, "8": 36, "9": 40, "10": 44 },
    // 三顺的权重
    plane: { "2": 18, "3": 24, "4": 30, "5": 36, "6": 42 },
    // 炸弹
    bomb: { "3": 13, "4": 14, "5": 15, "6": 16, "7": 17, "8": 18, "9": 19,
        "10": 20, "11": 21, "12": 22, "13": 24, "14": 26, "15": 28 },
    // 王炸
    joker: 30
};

/**
 * 牌型组合大小定义
 */
export enum GroupValue{
    unknow = -1,
    single = 1, // 单牌
    pairs = 1,   // 对子
    joker = 3, // 王炸
    three = 1, // 三不带 3,3,3
    three_single = 1, // 3带1  3,3,3,4
    three_pairs = 2, // 3带对 3,3,3,4,4
    bomb = 2, // 炸弹 3,3,3,3
    four_Single = 1, // 4带1   3,3,3,3,4
    four_pairs = 1, // 4带2对 3,3,3,3,4,4
    progression = 1, // 顺子 34567
    progression_pairs = 1, // 连对334455
    plane = 1, // 飞机不带333444
    plane_single = 1, // 飞机带单3334445
    plane_pairs = 2, // 飞机带对子33344455
}

/**
 * 单张扑克牌应包含这些基础数据
 */
export interface PokerData {
    num: number,   // 逻辑值
    val: number,   // 牌值
    suit: number,  // 花色值
    name: string,  // 花色名,对应enum PokerSuit值，用于开发调试
}

// 统计牌信息
export interface Census{
    val:number,   // 牌值
    count:number, // 数量
}

// 组合
export interface Union{
    type: PokerType,     // 组合类型
    weight:PokerWeight,
    cards:PokerData[],
    min: number,      // 最小值 55533=>5
}
// 无效牌组
export const InvalidUnion:Union = {cards:[],weight:PokerWeight.unknow,type:PokerType.unknow,min:-1};
const Name:string[]=['','','','3','4','5','6','7','8','9','10','J','Q','K','A','2','小王','大王'];
export class BaseLogic extends Singleton<BaseLogic>{
    constructor(){
        super();
    }

    /**
     * 将代表扑克逻辑数值的值转化为扑克数据
     * 并将牌从大到小从黑桃到方块进行排列
     * @param n 代表扑克的逻辑值
     * @returns PokerData
     */
    public num2Poker(n:number):PokerData{
        const maxIdx = Pokers.length-1;
        if (n > Pokers[maxIdx]){
            throw (new Error(`BaseLogin.num2Poker(${n})is invalid.`));
        }
        if (n === SpecialValue.imp || n === SpecialValue.king){
            const isImp = n === SpecialValue.imp;
            const col = isImp ? PokerSuit.imp : PokerSuit.king;
            return {num:n,val:n,suit:col,name:`${Name[isImp?16:17]}`};
        }

        let _suit = n & 0xf0;
        if (_suit === 0) _suit = PokerSuit.diamond;
        else if (_suit === 16) _suit = PokerSuit.heart;
        else if (_suit === 32) _suit = PokerSuit.club;
        else if (_suit === 48) _suit = PokerSuit.spade;
        
        let _val = n & 0x0f;
        _val < 3 && (_val += 13);
        let _name = `${PokerSuit[_suit]}-${Name[_val]}`;
        return {num:n,val:_val,suit:_suit,name:_name};
    }

    /**
     * 数组转牌组
     * @param list 
     * @returns 
     */
    public list2Pokers(list:number[],isSort:boolean=true):PokerData[]{
        if (!list){
            throw (new Error(`BaseLogin.list2Pokers(number[])is invalid.`));
        }
        let _datas:PokerData[] = [];
        for (let it of list) _datas.push(this.num2Poker(it));

        // 同值看花色
        if (isSort){
            _datas.sort((a,b)=>{
                return a.val === b.val ? a.suit - b.suit : b.val-a.val;
            });
        }
        return _datas;
    }

    /**
     * 获取牌组信息
     * @param list 
     * @returns 
     */
    public getUnion(list:number[]|PokerData[]):Union{
        if (!list || list.length < 1) return InvalidUnion;
        const cards = this._checkList(list);
        let _info:Union =  {cards:[],weight:PokerWeight.unknow,type:PokerType.unknow,min:-1};
        _info.cards = cards.slice(0);

        switch (cards.length){
            case 1:
                _info.type = PokerType.single;
                _info.weight = PokerWeight.single;
                _info.min = cards[0].val;
              return _info;
            case 2:{
                if (this.isPairs(cards)){
                    _info.type = PokerType.pairs;
                    _info.weight = PokerWeight.pairs;
                    _info.min = cards[0].val;
                    return _info;
                } else if (this.isJoker(cards)){
                    _info.type = PokerType.joker;
                    _info.weight = PokerWeight.joker;
                    _info.min = this.getMinVal(cards,1);
                    return _info;
                }
            }
            case 3:
              if (this.isThree(cards)){
                _info.type = PokerType.three;
                _info.weight = PokerWeight.three;
                _info.min = cards[0].val;
                return _info;
              }
            case 4:
              if (this.isThreeWithSingle(cards)) {
                _info.type = PokerType.three_single;
                _info.weight = PokerWeight.three_single;
                _info.min = this.getMinVal(cards,3);
                return _info;
              } else if (this.isBomb(cards)) {
                _info.type = PokerType.bomb;
                _info.weight = PokerWeight.bomb;
                _info.min = cards[0].val;
                return _info;
              }
            default:{
                if (this.isProgression(cards)){
                    _info.type = PokerType.progression;
                    _info.weight = PokerWeight.progression;
                    _info.min = this.getMinVal(cards,1);
                    return _info;
                }
                else if (this.isProgressionPairs(cards)){
                    _info.type = PokerType.progression_pairs;
                    _info.weight = PokerWeight.progression_pairs;
                    _info.min = this.getMinVal(cards,2);
                    return _info;
                }
                else if (this.isThreeWithPairs(cards)){
                    _info.type = PokerType.three_pairs;
                    _info.weight = PokerWeight.three_pairs;
                    _info.min = this.getMinVal(cards,3);
                    return _info;
  
                }
                else if (this.isPlane(cards)){
                    _info.type = PokerType.plane;
                    _info.weight = PokerWeight.plane;
                    _info.min = this.getMinVal(cards,3);
                    return _info;
                }
                else if (this.isPlaneWithSingle(cards)){
                    _info.type = PokerType.plane_single;
                    _info.weight = PokerWeight.plane_single;
                    _info.min = this.getMinVal(cards,3);
                    return _info;
                }
                else if (this.isPlaneWithPairs(cards)){
                    _info.type = PokerType.plane_pairs;
                    _info.weight = PokerWeight.plane_pairs;
                    _info.min = this.getMinVal(cards,3);
                    return _info;
                }
                else if (this.isFourWithSingle(cards)){
                    _info.type = PokerType.four_Single;
                    _info.weight = PokerWeight.four_Single;
                    _info.min = this.getMinVal(cards,4);
                    return _info;
                }
                else if (this.isFourWithPairs(cards)){
                    _info.type = PokerType.four_pairs;
                    _info.weight = PokerWeight.four_pairs;
                    _info.min = this.getMinVal(cards,4);
                    return _info;
                }
            }
        }
        return _info;
    }
    

    /**
     * 是不是对子
     * @param list 
     */
    protected isPairs(list:number[]|PokerData[]): boolean {
        const tmp = this._checkList(list);
        return tmp.length === 2 ?  tmp[0].val === tmp[1].val : false;
    }

    /**
     * 是不是三张
     * 333
     * @param list 
     */
     protected isThree(list:number[]|PokerData[]): boolean {
        const tmp = this._checkList(list);
        return tmp.length === 3 ?  tmp[0].val === tmp[2].val : false;
    }

    /**
     * 是不是三带一
     * 3334
     * @param list 
     * @returns 
     */
     protected isThreeWithSingle(list:number[]|PokerData[]): boolean {
        if (!list || list.length !== 4) return false;

        const tmp = this.valCount(list);
        return tmp.length === 2 && (tmp[0].count === 3 && tmp[1].count === 1);
    }

    /**
     * 是不是三带对
     * 33344
     * @param list 
     * @returns 
     */
     protected isThreeWithPairs(list:number[]|PokerData[]): boolean {
        if (!list || list.length !== 5) return false;

        const tmp = this.valCount(list);
        return tmp.length === 2 && (tmp[0].count === 3 && tmp[1].count === 2);
    }

    /**
     * 是不是王炸
     * @param list 
     */
     protected isJoker(list:number[]|PokerData[]): boolean {
        if (!list || list.length !== 2) return false;
        const tmp = this._checkList(list).sort((a,b)=>{return a.val-b.val});
        return tmp[0].val === SpecialValue.imp && tmp[1].val === SpecialValue.king;
    }

    /**
     * 是不是炸弹
     * @param list 
     * 3,3,3
     */
     protected isBomb(list:number[]|PokerData[]): boolean {
        const tmp = this._checkList(list);
        return tmp.length === 4 ?  tmp[0].val === tmp[3].val : false;
    }

    /**
     * 是否顺子 eg:3,4,5,6,7
     * @param list 
     * @returns 
     */
     protected isProgression(list:number[]|PokerData[]):boolean {
        if (list.length < 5 || list.length > 12) return false;
        const tmp = this._checkList(list);

        //不能包括大小王,和2
        for (let it of tmp){
            if (it.val >= SpecialValue.num_2){
                return false;
            }
        }
      
        // 是否连续
        for (var i = 0; i < tmp.length; i++) {
            if (i === tmp.length -1) break;
            if (Math.abs(tmp[i].val - tmp[i+1].val) !== 1) {
                return false;
            }
        }
      
        return true;
    }

    /**
     * 是否连队
     * 334455
     * @param list 
     * @returns 
     */
     protected isProgressionPairs(list:number[]|PokerData[]):boolean{
        if (list.length < 6 || list.length > 20 || list.length % 2) return false;
        const tmp = this._checkList(list);

        //不能包括大小王,和2
        for (let it of tmp){
            if (it.val >= SpecialValue.num_2){
                return false;
            }
        }
        let map = {};
        for (let i = 0; i < tmp.length; ++i) {
            map.hasOwnProperty(tmp[i].val)?map[tmp[i].val]++:map[tmp[i].val] = 1;
        }

        //相同牌只能是2张
        for (let key in map) {
            if (map[key] !== 2) return false;
        }
        const keys = Object.keys(map);
        if (keys.length < 3) return false;

        //对子之间相减绝对值只能是1
        for (let i = 0; i < keys.length; ++i) {
            if (i === keys.length - 1) break;
            if (Math.abs(parseInt(keys[i]) - parseInt(keys[i+1])) !== 1) {
                return false;
            }
        }

        return true;
    }

    /** 是否飞机
     * 333444
     * @param list 
     * @returns 
     */
     protected isPlane(list:number[]|PokerData[]):boolean {
        const tmp = this._checkList(list).sort((a,b)=>{return b.val-a.val});
        if (tmp.length < 6 || tmp.length % 3 !== 0 || tmp[0].val >= SpecialValue.num_2) return false;
        for (let i = 0; i < tmp.length; i += 3) {
            if (i !== (tmp.length - 3) && (tmp[i].val !== tmp[i + 1].val || tmp[i].val !== tmp[i + 2].val || (tmp[i].val - 1) !== tmp[i + 3].val)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 
     * 33344456
     * @param list 
     * @returns 
     */
     protected isPlaneWithSingle(list:number[]|PokerData[]):boolean {
        if (list.length < 8 || list.length % 4 !== 0) return false;
        const tmp = this.valCount(list);
        let three = [], count = tmp.length / 4;
        for (let it of tmp) it.count === 3 && three.push(it);

        //检测三条数量和不能为2
        if (three.length !== count || three[0].val >= SpecialValue.num_2) return false;
        //检测三根是否连续
        for (let i = 0; i < three.length-1; ++i) {
            if (three[i].val - 1 !== three[i + 1].val) return false;
        }
        return true;
    }

    /**是否是飞机带对
     * 3334445566
     * @param list 
     * @returns 
     */
     protected isPlaneWithPairs(list:number[]|PokerData[]):boolean {
        if (list.length < 10 || list.length % 5 != 0) return false;
        const tmp = this.valCount(list);
        let three = [], pairs = [], groupCount = tmp.length / 5;
        for (let it of tmp){
            if (it.count === 3)  three.push(it);
            else if (it.count === 2) pairs.push(it);
            else return false;
        }

        //检测三条数量和对子数量和不能为2
        if (three.length != groupCount || pairs.length != groupCount || three[0].val === SpecialValue.num_2) return false;
        
        //检测三条是否连续
        for (let i = 0; i < three.length-1; ++i) {
            if (three[i].val - 1 != three[i + 1].val) return false;
        }
        return true;
    }
    
    /**是否是四带二
     * 333345
     * @param list 
     * @returns 
     */
     protected isFourWithSingle(list:number[]|PokerData[]):boolean {
        const tmp = this.valCount(list);
        if (list.length !== 6 || tmp.length > 3) return false;
        if (tmp.length !== 3) return false;
        for (let it of tmp){
            if (it.count === 4) return true;
        }

        return false;
    };
    //是否是四带两个对
    /**
     * 33334455
     * @param list 
     * @returns 
     */
     protected isFourWithPairs(list:number[]|PokerData[]):boolean {
        if (list.length !== 8) return false;
        const tmp = this.valCount(list);
        if (tmp.length !== 3) return false;
        for (let it of tmp){
            if (it.count !== 4  && it.count !== 2) return false;
        }
        return true;
    }
    
    /**
     * 牌统计，统计各个牌有多少张，比如2张A，一张8
     * @param  {list} list - 要统计的牌
     * @return {object array} val：值，count：数量
     * 7778899=>[{val:7,count:3},{val:8,count:2},{val:9,count:2}]
     */
     protected valCount(list:number[] | PokerData[]):Census[]{
        let result:Census[] = [];
        const addCount = (result, v)=>{
            for (let i = 0; i < result.length; i++) {
                if (result[i].val === v) {
                    result[i].count++;
                    return;
                }
            }
            result.push({val: v, count: 1 });
        };
        const tmp = this._checkList(list);
        for (let it of tmp) addCount(result, it.val);
        result.sort((a,b)=>{
            if (a.count === b.count) return a.val - b.val; else return b.count - a.count}
        );
        return result;
    }

    /**
     * 获取指定张数的最小牌值
     * @param  {list} list - 牌组
     * @param  {list} cont - 张数
     * @return 值
     */
     protected getMinVal(list:number[] | PokerData[], cont:number):number {
        const tmp = this.valCount(list);
        let min = 0;
        for (let it of tmp) {
            (it.count === cont && it.val < min) && (min = it.val);
        }
        return min;
    }

    public sort(list:number[]|PokerData[],isBigFist:boolean=true):PokerData[] {
        return this._checkList(list).sort((a:PokerData, b:PokerData)=>{ 
                return a.val === b.val ? a.suit-b.suit : (isBigFist ? b.val-a.val : a.val-b.val);
        });
    }

    /** 判断牌是否连续 */
    protected isContinous(list:number[] | PokerData[]) {
        const tmp = this.sort(list);
        for (let i = 0; i < tmp.length - 1; ++i) {
            if (tmp[i+1].val - tmp[i].val !== 1)
                return false;
        }
        return true;
    }

    // 获取连续顺序元素个数
    protected getContinousCount(list:number[] | PokerData[]):number {
        const tmp = this.sort(list);
        let _count = 0;
    
        for (let i = 0; i < tmp.length - 1; ++i) {
            tmp[i+1].val - tmp[i].val === 1 && _count++;
        }
        return _count;
    }
    
    /** 判断牌是否都小于2 */
    protected isLessThan2(list:number[] | PokerData[]):boolean {
        const tmp = this._checkList(list);
        for (let i = 0; i < tmp.length; ++i){
            if (tmp[i].val >= SpecialValue.num_2)
                return false;
        }
        return true;
    }
    /**
     *
     * 将扑克牌拆分成基础牌型
     * 单牌，对子 三张 炸弹 王炸
     * @protected
     * @param {(number[]|PokerData[])} list
     * @param {boolean} [isFistBid=true] 默认从大到小排序
     * @returns {Map<PokerType,Union[]>}
     * @memberof BaseLogic
     */
    protected split(list:number[]|PokerData[],isFistBid:boolean=true):Map<PokerType,Union[]>{
        let tmp:PokerData[] = this.sort(list).slice(0);

        let tmpMap:Map<PokerType,Union[]> = new Map<PokerType,Union[]>();
        const union = (list:PokerData[],type:PokerType,mv:number)=>{
            let weight:number = 0;
            const subWeight = WEIGHT[PokerType[type]];
            if (subWeight){
                for (let it of list){
                    weight += subWeight[`${it.val}`] || 0;
                }
            }
            const item:Union = {
                cards:list,
                type:type,
                weight:weight,
                min:mv
            }

            let find:Union[] = tmpMap.find(type); 
            !find ? tmpMap.inset(type,[]).push(item) : find.push(item);
        };


        let index = 0;
        let len = tmp.length;
        while (index < len) {
            if (index + 1 < len){
                // 王炸
                if (tmp[index].val === SpecialValue.king && tmp[index+1].val === SpecialValue.imp){
                    union([tmp[index],tmp[index+1]],PokerType.joker,SpecialValue.imp);
                    index += 2;
                } else if (tmp[index].val === tmp[index + 1].val) {
                    if (index + 2 < len && tmp[index + 1].val === tmp[index + 2].val) {
                        if (index + 3 < len && tmp[index + 2].val === tmp[index + 3].val) {
                            union([tmp[index],tmp[index+1],tmp[index+2],tmp[index+3]],PokerType.bomb,tmp[index].val);
                            index += 4;
                        }
                        else {
                            union([tmp[index],tmp[index+1],tmp[index+2]],PokerType.three,tmp[index].val);
                            index += 3;
                        }
                    } else {
                        union([tmp[index],tmp[index+1]],PokerType.pairs,tmp[index].val);
                        index += 2;
                    }
                } else {
                    union([tmp[index]],PokerType.single,tmp[index].val);
                    index += 1;
                }
            } else {
                union([tmp[index]],PokerType.single,tmp[index].val);
                index += 1;
            }
        }

        tmpMap.sort((a,b)=>{
            if (isFistBid){
                return a.Key === b.Key ? b.Val[0].cards[0].val - a.Val[0].cards[0].val : b.Key - a.Key;
            } else {
                return a.Key === b.Key ? a.Val[0].cards[0].val - b.Val[0].cards[0].val : a.Key - b.Key;
            }
        });
        
        return tmpMap;
    }

    /**
     * 检查列表，统一为扑克数据
     * @param list 
     * @param isSort 
     * @returns 
     */
    private _checkList(list:number[] | PokerData[]):PokerData[]{
        if (!list || list.length < 1) return [];
        let tmp = [];
        if (typeof(list[0]) === 'number') tmp = this.list2Pokers(list as number[],false);
        if (typeof(list[0]) === 'object') tmp = list as PokerData[];
        return tmp;
    }
}